package com.zyl.future;

import java.util.UUID;
import java.util.concurrent.*;

/**
 * @author zhaoyl
 * @date 2024/5/17 21:53
 * @description 说明 ：异步回调
 * CompletableFuture处理任务之间的逻辑关系
 *
 * Thread + Runnable：执行异步任务，但是没有返回结果
 * Thread + Callable + FutureTask：完整一个可以有返回结果的异步任务
 * 获取返回结果，如果基于get方法获取，线程需要挂起在WaitNode里
 * 获取返回结果，也可以基于isDone判断任务的状态，但是这里需要不断轮询
 * 上述的方式都是有一定的局限性的。
 *
 * CompletableFuture就是帮你处理这些任务之间的逻辑关系，编排好任务的执行方式后，任务会按照规划好的方式一步一步执行，
 * 不需要让业务线程去频繁的等待。
 *比如说任务A，任务B，还有任务C。其中任务B还有任务C执行的前提是任务A先完成，再执行任务B和任务C。
 * CompletionStage接口定义了任务编排的方法，执行某一阶段，可以向下执行后续阶段。异步执行的，默认线程池是ForkJoinPool.commonPool()，
 * 但为了业务之间互不影响，且便于定位问题，强烈推荐使用自定义线程池。
 *
 * Supplier<U>  // 生产者，没有入参，有返回结果
 * Consumer<T>  // 消费者，有入参，但是没有返回结果
 * Function<T,U>// 函数，有入参，又有返回结果
 */
public class Demo01 {
    public static void main(String[] args) {
        //runAsyncTest();
        supplyAsyncTest();
    }

    public static void runAsyncTest() {
        //runAsync：当前方式既不会接收参数，也不会返回任何结果，非常基础的任务编排方式
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(
                () -> {
                    try {
                        TimeUnit.SECONDS.sleep(2);//执行2秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + Thread.currentThread().getName() + "->runAsync=>Void");//异步执行
                });
        System.out.println("--------我先执行了----------");
        try {
            completableFuture.get();//获取执行结果，会阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    //CompletableFuture如果不提供线程池的话，默认使用的ForkJoinPool，而ForkJoinPool内部是守护线程，
    // 如果main线程结束了，守护线程会跟着一起结束。
    public static void supplyAsyncTest() {

        // 生产者，可以指定返回结果
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);//执行1秒

                int i = 10 / 0;//这里报个错----------------------

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + "->supplyAsync=>异步任务结束");//异步执行
            return 200;
        });
        /*--------------------------------------*/
        try {
            System.out.println(
                    supplyAsync.whenComplete((t, u) -> {
                        System.out.println("t->" + t);//正常返回结果：200;报错误返回null
                        System.out.println("u->" + u);//正常返回null，报错误则返回错误信息j:ava.lang.ArithmeticException: / by zero
                    }).exceptionally((e) -> {   //异常则输出
                        System.out.println(e.getMessage());
                        return 500;     //报错返回
                    }).get());//获取结果（程序正常200，报错500）
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

            /*
            * 有任务A，还有任务B。
        任务B需要在任务A执行完毕后再执行。
        而且任务B需要任务A的返回结果。
        任务B自身也有返回结果。
        thenApply可以拼接异步任务，前置任务处理完之后，将返回结果交给后置任务，然后后置任务再执行
        thenApply提供了带有Async的方法，可以指定每个任务使用的具体线程池。
        */
    public static void thenApplyTest() {
        CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
            String id = UUID.randomUUID().toString();
            System.out.println("执行任务A：" + id);
            return id;
        });
        CompletableFuture<String> taskB = taskA.thenApply(result -> {
            System.out.println("任务B获取到任务A结果：" + result);
            result = result.replace("-", "");
            return result;
        });

        System.out.println("main线程拿到结果：" + taskB.join());

    }
    //thenApplyAsync（自定义线程池）
    public static void thenApplyAsyncTest() {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        CompletableFuture<String> taskB = CompletableFuture.supplyAsync(() -> {
            String id = UUID.randomUUID().toString();
            System.out.println("执行任务A：" + id + "," + Thread.currentThread().getName());
            return id;
        }).thenApplyAsync(result -> {
            System.out.println("任务B获取到任务A结果：" + result + "," + Thread.currentThread().getName());
            result = result.replace("-", "");
            return result;
        },executor);

        System.out.println("main线程拿到结果：" + taskB.join());
    }
   /**
    * 其他：
    *  thenAccept，thenAcceptAsync（自定义线程池）
    * 套路和thenApply一样，都是任务A和任务B的拼接
    * 前置任务需要有返回结果，后置任务会接收前置任务的结果，返回后置任务，没有返回值
    *
    *  thenRun，thenRunAsync（自定义线程池）
    * 套路和thenApply，thenAccept一样，都是任务A和任务B的拼接
    * 前置任务没有返回结果，后置任务不接收前置任务结果，后置任务也没有返回结果。
    *
    * thenCombine，thenAcceptBoth，runAfterBoth
    * 比如有任务A，任务B，任务C。任务A和任务B并行执行，等到任务A和任务B全部执行完毕后，再执行任务C。
    *  thenCombine
    * 当前方式当前方式前置任务需要有返回结果，后置任务接收前置任务的结果，有返回值
    *  thenAcceptBoth
    * 当前方式前置任务需要有返回结果，后置任务接收前置任务的结果，没有返回值
    *  runAfterBoth
    * 当前方式前置任务不需要有返回结果，后置任务不会接收前置任务的结果，没有返回值
    *
    * applyToEither，acceptEither，runAfterEither
    * 这三个方法：比如有任务A，任务B，任务C。任务A和任务B并行执行，只要任务A或者任务B执行完毕，开始执行任务C
    * applyToEither：可以接收结果并且返回结果
    * acceptEither：可以接收结果没有返回结果
    * runAfterEither：不接收结果也没返回结果
    * 三个方法拼接任务的方式都是一样的
    *
    * exceptionally，thenCompose，handle
    * exceptionally：
    * 这个也是拼接任务的方式，但是只有前面业务执行时出现异常了，才会执行当前方法来处理
    * 只有异常出现时，CompletableFuture的编排任务没有处理完时，才会触发。
    * 拿不到任务结果。
    * whenComplete，handle：
    * 这两个也是异常处理的套路，可以根据方法描述发现，他的功能方向比exceptionally要更加丰富
    * whenComplete可以拿到返回结果同时也可以拿到出现的异常信息，但是whenComplete本身是Consumer不能返回结果。无法帮你捕获异常，但是可以拿到异常返回的结果。
    * handle可以拿到返回结果同时也可以拿到出现的异常信息，并且也可以指定返回托底数据。可以捕获异常的，异常不会抛出去。
    *
    * allOf，anyOf
    * allOf：
    * allOf的方式是让内部编写多个CompletableFuture的任务，多个任务都执行完后，才会继续执行你后续拼接的任务
    * allOf返回的CompletableFuture是Void，没有返回结果
    *
    * anyOf：
    * anyOf是基于多个CompletableFuture的任务，只要有一个任务执行完毕就继续执行后续，最先执行完的任务做作为返回结果的入参
    * */

}



